﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;

using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using Common;

namespace ImageSpaceFiltersPlugin
{
    public partial class RegionGrowForm : Form
    {
        public RegionGrowForm()
        {
            InitializeComponent();
        }

        enum Tool
        {
            Hand, PlantSeed
        }

        private Bitmap bitmap = null;
        private bool[,] mask;
        private Color[,] pixels;
        private List<bool[,]> maskStack = new List<bool[,]>();

        public Bitmap Bitmap
        {
            get
            {
                return bitmap;
            }
            set
            {
                bitmap = value;
                mask = new bool[bitmap.Height, bitmap.Width];
                maskBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
                for (int i = 0; i < bitmap.Height; i++)
                    for (int j = 0; j < bitmap.Width; j++)
                        mask[i, j] = false;
                BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly,
                     System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                pixels = new Color[bitmap.Height, bitmap.Width];
                for (int i = 0; i < bitmap.Height; i++)
                {
                    unsafe
                    {
                        int* pix = (int*)((int)data.Scan0 + data.Stride * i);
                        for (int j = 0; j < bitmap.Width; j++)
                        {
                            pixels[i, j] = Color.FromArgb(pix[j]);
                        }
                    }
                }
                bitmap.UnlockBits(data);
                Document doc = new Document();
                doc.SetBitmap(bitmap);
                imageView.SetDocument(doc);
                imageView.ViewMode = ViewMode.BestFit;
            }
        }

        int GetColorDistance(Color c1, Color c2)
        {
            return Math.Max(Math.Max(Math.Abs(c1.R - c2.R), Math.Abs(c1.G - c2.G)),
                Math.Abs(c1.B - c2.B));
        }

        private delegate void IncludeToRegion(int x, int y);
        void GrowRegion()
        {
            PushMask();
            int y = startPoint.Y;
            int x = startPoint.X;
            double threshold = (double)similarityUpDown.Value/100.0;
            mask[y, x] = true;
            bool[,] scanned = new bool[bitmap.Height, bitmap.Width];
            for (int i = 0; i < bitmap.Height; i++)
                for (int j = 0; j < bitmap.Width; j++)
                    scanned[i, j] = false;
            scanned[y, x] = true;
            
            Queue<Point> queue = new Queue<Point>();
            Point curPoint = startPoint;
            //double stdev = 0.0;
            //double sn = 0.0;
            double meanR = pixels[y,x].R;
            double meanG = pixels[y,x].G;
            double meanB = pixels[y,x].B;
            int n = 1;

            IncludeToRegion include = delegate(int X, int Y)
            {
                double diff = Math.Max(Math.Abs(pixels[Y, X].R - meanR)/meanR,
                    Math.Max(Math.Abs(pixels[Y,X].B-meanB)/meanB,
                    Math.Abs(pixels[Y, X].G - meanG)/meanG));

                if (diff<threshold)
                {
                    mask[Y, X] = true;
                    queue.Enqueue(new Point(X,Y));
                    meanR = (meanR * n + pixels[Y, X].R) / (n + 1);
                    meanG = (meanG * n + pixels[Y, X].G) / (n + 1);
                    meanB = (meanB * n + pixels[Y, X].B) / (n + 1);
                    //sn = sn + (val - mean)*(val-newMean);
                    //stdev = Math.Sqrt(sn/(n));
                    n++;
                }
            };
            while(true)
            {
                Point p1, p2, p3, p4;
                p1 = new Point(curPoint.X - 1, curPoint.Y);
                p2 = new Point(curPoint.X + 1, curPoint.Y);
                p3 = new Point(curPoint.X, curPoint.Y - 1);
                p4 = new Point(curPoint.X, curPoint.Y + 1);
                if (p1.X >= 0 && !scanned[p1.Y, p1.X])
                {
                    scanned[p1.Y, p1.X] = true;
                    include(p1.X, p1.Y);
                }
                if (p2.X < bitmap.Width && !scanned[p2.Y, p2.X])
                {
                    scanned[p2.Y, p2.X] = true;
                    include(p2.X, p2.Y);
                }
                if (p3.Y >= 0 && !scanned[p3.Y, p3.X])
                {
                    scanned[p3.Y, p3.X] = true;
                    include(p3.X, p3.Y);
                }
                if (p4.Y < bitmap.Height && !scanned[p4.Y, p4.X])
                {
                    scanned[p4.Y, p4.X] = true;
                    include(p4.X, p4.Y);
                }
                if (queue.Count != 0)
                    curPoint = queue.Dequeue();
                else
                    break;
            }

            UpdateMaskBitmap();
            imageView.Refresh();
        }

        Bitmap maskBitmap = null;

        private void UpdateMaskBitmap()
        {
            BitmapData data = maskBitmap.LockBits(new Rectangle(0, 0, maskBitmap.Width, maskBitmap.Height),
                 ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            for (int i = 0; i < maskBitmap.Height; i++)
            {
                unsafe
                {
                    int* pix = (int*)((int)data.Scan0 + data.Stride * i);
                    for (int j = 0; j < maskBitmap.Width; j++)
                    {
                        pix[j] = Color.FromArgb(mask[i, j] ? 128 : 0, Color.Blue).ToArgb();
                    }
                }
            }
            maskBitmap.UnlockBits(data);
        }

        public bool GetMask(int x, int y)
        {
            return mask[y, x];
        }

        const int MaxUndoCount = 10;

        void PushMask()
        {
            maskStack.Add(mask);
            bool[,] orimask = mask;
            mask = new bool[bitmap.Height, bitmap.Width];
            for (int i = 0; i < bitmap.Height; i++)
            {
                for (int j = 0; j < bitmap.Width; j++)
                {
                    mask[i, j] = orimask[i, j];
                }
            }
            if (maskStack.Count > MaxUndoCount)
            {
                maskStack.RemoveAt(0);
            }
        }

        void PopMask()
        {
            if (maskStack.Count > 0)
            {
                mask = maskStack[maskStack.Count - 1];
                maskStack.RemoveAt(maskStack.Count - 1);
            }
        }

        Tool curTool = Tool.Hand;

        private void moveToolButton_Click(object sender, EventArgs e)
        {
            curTool = Tool.Hand;
            //imageView.Cursor = new Cursor(imageView.GetType(), "curDown.cur");
            imageView.EnableMoving = true;
            plantSeedToolButton.Checked = false;
        }

        private void plantSeedToolButton_Click(object sender, EventArgs e)
        {
            curTool = Tool.PlantSeed;
            imageView.Cursor = Cursors.Cross;
            imageView.EnableMoving = false;
            moveToolButton.Checked = false;
        }

        public Point startPoint = new Point(-1,-1);

        private void imageView_ImageMouseDown(int x, int y)
        {
            if (curTool == Tool.PlantSeed)
            {
                if (x >= 0 && y >= 0 && x < bitmap.Width && y < bitmap.Height)
                {
                    startPoint.X = x;
                    startPoint.Y = y;
                    imageView.Refresh();
                }
            }
        }

        private void imageView_DrawOverlay(Graphics g, Rectangle imageRect)
        {
            if (maskBitmap != null)
            {
                g.DrawImage(maskBitmap, imageRect);
                if (startPoint.X >= 0 && startPoint.Y >= 0)
                {
                    Point p = imageView.ImageSpacePointToScreenSpacePoint(startPoint);
                    g.FillEllipse(new SolidBrush(Color.FromArgb(128,255,0,0)),
                        new Rectangle(p.X-4,p.Y-4,8,8));
                }
            }
        }

        private void undoButton_Click(object sender, EventArgs e)
        {
            Undo();
        }

        private void Undo()
        {
            PopMask();
            UpdateMaskBitmap();
            imageView.Refresh();
        }

        private void zoomIn_Click(object sender, EventArgs e)
        {
            imageView.ZoomFactor++;

        }

        private void zoomOut_Click(object sender, EventArgs e)
        {
            imageView.ZoomFactor--;
        }

        private void growRegionButton_Click(object sender, EventArgs e)
        {
            Cursor = Cursors.WaitCursor;
            GrowRegion();
            Cursor = Cursors.Default;
        }

        private void okButton_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
        }

        private void cancelButton_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
        }

        private void colorPanel_Paint(object sender, PaintEventArgs e)
        {
            
        }

        public Color GetBackgroundColor()
        {
            return colorPanel.BackColor;
        }

        private void colorPanel_Click(object sender, EventArgs e)
        {
            if (colorDialog.ShowDialog() == DialogResult.OK)
                colorPanel.BackColor = colorDialog.Color;
        }

        private void inverseSelectionButton_Click(object sender, EventArgs e)
        {
            for (int i=0; i<bitmap.Height; i++)
                for (int j = 0; j < bitmap.Width; j++)
                {
                    mask[i, j] = !mask[i, j];
                }
            UpdateMaskBitmap();
            imageView.Refresh();
        }
    }
}
